package com.wangxin.study8.lock;

import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName ReentrantRedisLock
 * @Description 分布式可重入锁
 * @Author jojo.wang
 * @Date 2019-05-29 19:40
 * @Version 1.0
 **/
@Component
public class ReentrantRedisLock implements ReentrantRedisLockService{
    // TODO: 2019-05-29 注入redis的service类,此处只做了一个假的service
    @Resource
    private RedisService redisService;

    private static ThreadLocal<String> TOKEN_MAP = new ThreadLocal<>();

    /**
     * 锁前缀
     */
    private static final String LOCK_PREFIX = "LOCK_";

    /**
     * 本机ip
     */
    private static String IP;

    private static final long MAX_TIME_OUT = Long.MAX_VALUE;

    static {
        try {
            IP = InetAddress.getLocalHost().getHostAddress();
        }catch (Exception e){
            IP = "UNKNOWN";
        }
    }


    @Override
    public boolean tryLock(String key, long timeOut, TimeUnit timeUnit, long expire) {
        if (tryLock(key,expire)){
            return true;
        }
        boolean temp = false;
        long startTime = System.nanoTime();

        /**
         * 自旋判断
         * 是否获取到了锁 && 是否超时
         */
        while (!temp && (System.nanoTime()-startTime)<timeUnit.toNanos(timeOut)){
            temp = getLock(key,expire);
        }
        return temp;
    }

    @Override
    public boolean tryLock(String key, long expire) {
        if (getLock(key,expire)){
            return true;
        }
        if (getToken().equals(getLockToken(key))){
            return true;
        }
        return false;
    }

    @Override
    public void lock(String key, long expireSecs) {
        tryLock(key,MAX_TIME_OUT,TimeUnit.SECONDS,expireSecs);
    }

    @Override
    public boolean unlock(String key) {
        if (getToken().equals(getLockToken(key))){
            List<String> keys = new ArrayList<>(1);
            keys.add(key(key));
            List<String> args = new ArrayList<>(1);
            args.add(TOKEN_MAP.get());

            boolean result = redisService.del(keys, args);
            if (result){
                TOKEN_MAP.remove();
            }
            return result;
        }
        return false;
    }

    private String getToken(){
        String token = TOKEN_MAP.get();
        if (null == token ){
            TOKEN_MAP.set(token=IP+"_"+ UUID.randomUUID().toString().replaceAll("-",""));
        }
        return token;
    }

    private String getLockToken(String key){
        return redisService.get(key);
    }

    private boolean getLock(String key, long expire){
        return "OK".equals(redisService.set(key(key),getToken(),"NX", "EX", expire));
    }

    private String key(String key){
        return LOCK_PREFIX+key;
    }
}
